perm filename SIMUL[TIM,LSP] blob sn#577513 filedate 1981-04-02 generic text, type T, neo UTF8
; Simulation package.				-*-Mode:LISP; Base:10-*-

; This file includes:
; 1. Random number utilities.
; 2. A simulation driver.
; 3. A queue manager.
; 4. A simple M/M/1 test system.

; Set reasonable number base.
(eval-when (compile load eval)
 (setq base (+ 8 2) ibase (+ 8 2) *nopoint t))

; Output macros to FASL file.
#m(declare (macros t))
 
#m(eval-when (compile load eval)
 (load "alan;struct fasl"))

(defmacro increment (var &optional (delta 1))
  `(setf ,var (+ ,var ,delta)))

(defmacro decrement (var &optional (delta 1))
  `(setf ,var (- ,var ,delta)))

#m(declare (flonum (random-float flonum)))
(defun random-float ()
 (//$ (float (1+ (random 1000000)))
      1000000.0))

#m(declare (flonum (random-exponential flonum)))
(defun random-exponential (mean)
 (-$ (*$ (log (random-float))
	 mean)))


(defun test (form &optional (N 1000))
 (prog (sum sumsq)
  (setq sum 0.0 sumsq 0.0)
  (do i N (1- i) (= i 0)
      (let ((x (eval form)))
	   (setq sum (+$ sum x))
	   (setq sumsq (+$ sumsq (*$ x x)))
	   ))
  (let ((x (//$ sum (float N))))
       (let ((y (sqrt (-$ (//$ sumsq (float N))
			  (*$ x x)))))
	    (terpri)
	    (format t "Average = }s, standard deviation = }s" x y)))))

;;; Simulation Driver


; EVENT-LIST is a list of (TIME . EVENT) pairs sorted by TIME.
; EVENT-AT-TIME adds to this list, and RUN removes things from it.
; It is initialized by RUN.
(declare (special event-list))

; (EVENT-AT-TIME EVENT TIME) causes EVENT to be funcall'd at TIME.
(defun event-at-time (event time)
 (cond ((or (null event-list)
	    (> (caar event-list) time))
	(setq event-list (cons (cons time event) event-list)))
       (t (do ((i event-list (cdr i)))
	      ((or (null (cdr i))
		   (> (caadr i) time))
	       (rplacd i (cons (cons time event) (cdr i))))
	      ))))


; CURRENT-TIME is set by RUN, and read by many event subroutines.  It
; represents time in the simulation run.
; CURRENT-EVENT is the name of the current event.  It is currently used
; only in RUN.
(declare (special current-time current-event))
(defvar event-trace ())

; (RUN DURATION) does a simulation run, terminating after DURATION clock
; units.  The user subroutine GENESIS is invoked at the beginning of time,
; and APOCALYPSE at the end of the simulation.  APOCALYPSE should
; finish with (*THROW 'DONE ()) to exit RUN.

(defun run (duration)
 (setq event-list ())				; no events yet
 (setq current-time 0)				; start time at zero for
						; error check below
 (event-at-time #'genesis 0)			; GENESIS will invoke other
						; events
 (event-at-time #'apocalypse duration)		; APOCALYPSE after the
						; specified length of time
 (*catch 'done
  (do ()					; loop
      ((null event-list)			; if EVENT-LIST becomes null
       (format t "}%Premature end of world")	; then terminate abnormally
       (apocalypse))				; still call user subroutine
      ; get next event from EVENT-LIST and do it
      (cond ((< (caar event-list) current-time)
	     (error "Attempt to warp time!")))
      (setq current-time (caar event-list)	; set CURRENT-TIME to time of
						; next event
	    current-event (cdar event-list)	; set CURRENT-EVENT to
						; subroutine to call
	    event-list (cdr event-list))
      (if event-trace
	  (format t "}%T = }s, calling }s" current-time current-event))
      (funcall current-event))			; call event subroutine
  ))

;;; Queues

(defstruct (queue #q :named) 
  (queue-list ())
  (queue-length 0)
  (queue-last-operation 0)
  (queue-time-length-product 0)
  (maximum-queue-length 0)
  queue-name
  )
  
(defun create-queue (name)
  (make-queue queue-name name))

(defun update-queue-statistics (queue)
  (increment (queue-time-length-product queue)
	     (* (- current-time (queue-last-operation queue))
		(queue-length queue)))
  (setf (queue-last-operation queue) current-time))

(defun enqueue (object queue)
  (update-queue-statistics queue)
  (setf (queue-list queue) (nconc (queue-list queue) (ncons object)))
  (increment (queue-length queue))
  (setf (maximum-queue-length queue) (max (maximum-queue-length queue)
					  (queue-length queue))))

(defun dequeue (queue)
  (if (null (queue-list queue))			; if nothing in queue
      ()					; return ()
      (prog1 (car (queue-list queue))
	     (update-queue-statistics queue)
	     (setf (queue-list queue) (cdr (queue-list queue)))
	     (decrement (queue-length queue)))))

(defun print-queue-statistics (queue)
  (update-queue-statistics queue)
  (format t "}%}a length: average = }s, maximum = }s"
	  (queue-name queue)
	  (//$ (float (queue-time-length-product queue))
	       (float current-time))
	  (maximum-queue-length queue)))

;;; M/M/1 test system.


; Requests are currently just a fixnum.  Should be a structure for
; tracking request service time, etc.
(defun create-request (service-time) service-time)
(defun service-time (request) request)


; Define/default mean service time and arrival rate.
(defvar mean-service-time 900.0)
(defvar mean-arrival-interval 1000.0)

(defun set-utilization (u)
 (setq mean-service-time (*$ mean-arrival-interval u)))


(defvar busy)					; set if server is busy
(defvar server-queue)				; used to queue requests
						; while server is busy
(defvar server-time)				; total time sever busy
(defvar start-of-service)			; time last service began

; Event for arrival of a new request.
(defun arrival ()
 (let ((request (create-request (fix (random-exponential mean-service-time))))
       (next (fix (random-exponential mean-arrival-interval))))
      (event-at-time #'arrival (+ current-time next))
      (if busy
	  (enqueue request server-queue)
	  (service request))
      ))

; Service a request.
(defun service (request)
 (setq start-of-service current-time)
 (setq busy t)
 (event-at-time #'departure (+ current-time (service-time request))))

; Event for service completion.  Update statistics and service next request
; in queue, if any.
(defun departure ()
 (increment server-time (- current-time start-of-service))
 (setq busy ())
 (let ((request (dequeue server-queue)))
   (if (not (null request))
       (service request))))

(defun print-server-statistics ()
 (if busy (increment server-time (- current-time start-of-service)))
 (format t "}%server utilization = }s"
	 (//$ (float server-time) (float current-time))))

(defun Genesis ()
 (format t "}%Begin queuing simulation with utilization = }s}%"
	 (//$ mean-service-time
	      mean-arrival-interval))
 (setq server-queue (create-queue "server queue"))	; create queue
 (setq busy ())					; mark server as idle
 (setq server-time 0)
 (arrival))					; start off with an arrival

(defun Apocalypse ()
 (format t "}%End simulation at T = }s}%" current-time)
 (print-server-statistics)
 (print-queue-statistics server-queue)
 (*throw 'done ()))
ββ